package com.laamella.syntherface.synths.models;

import java.util.Map;
import java.util.TreeMap;

import javax.sound.midi.InvalidMidiDataException;
import javax.swing.AbstractSpinnerModel;
import javax.swing.DefaultBoundedRangeModel;
import javax.swing.SpinnerModel;
import javax.swing.JToggleButton.ToggleButtonModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import com.laamella.syntherface.synths.events.SynthWideEvents;

public abstract class ParameterModel extends DefaultBoundedRangeModel implements ChangeListener {

	private Integer lastValue = null;
	public final String name;
	public final SpinnerModel spinnerModel;
	public final ToggleButtonModel buttonModel;
	private final ParameterType type;
	private final int defaultValue;

	public static final Map<String, String> parameterDocumentation = new TreeMap<String, String>();

	@SuppressWarnings("serial") public ParameterModel(String name, int defaultValue, ParameterType type) {
		super(defaultValue < type.minimum ? type.minimum : defaultValue, 0, type.minimum, type.maximum);
		this.name = name;
		this.defaultValue = defaultValue < type.minimum ? type.minimum : defaultValue;
		spinnerModel = new AbstractSpinnerModel() {

			@Override public Object getNextValue() {
				return bound(ParameterModel.this.getValue() + 1);
			}

			private int bound(int value) {
				if (value > getMaximum()) {
					return getMaximum();
				}
				if (value < getMinimum()) {
					return getMinimum();
				}
				return value;
			}

			@Override public Object getPreviousValue() {
				return bound(ParameterModel.this.getValue() - 1);
			}

			@Override public Object getValue() {
				return ParameterModel.this.getValue();
			}

			@Override public void setValue(Object obj) {
				ParameterModel.this.setValue((Integer) obj);
			}
		};
		buttonModel = new ToggleButtonModel() {

			@Override public void setSelected(boolean flag) {
				super.setSelected(flag);
				ParameterModel.this.setValue(flag ? 1 : 0);
			}
		};
		this.type = type;
		addChangeListener(this);
		SynthWideEvents.newParameterRegistered().send(this);
	}

	public ParameterModel(String name, int initialValue) {
		this(name, initialValue, ParameterType.OnOff);
	}

	@SuppressWarnings("serial") public ParameterModel(String name, ParameterType type) {
		this(name, 0, type);
	}

	public ParameterModel(String name) {
		this(name, 0);
	}

	public String getFormattedValue() {
		return type.format(getValue());
	}

	@Override public final void stateChanged(ChangeEvent e) {
		try {
			if (lastValue == null || lastValue.intValue() != getValue()) {
				String last = lastValue == null ? "???" : type.format(lastValue);
				updateStatusDisplay(name + ": " + last + " -> " + getFormattedValue());
				updateSynthesizer(getValue());
				lastValue = getValue();
			}
		} catch (InvalidMidiDataException e1) {
			SynthWideEvents.exception().send(e1);
		}
	}

	public void updateStatusDisplay(String text) {
		SynthWideEvents.statusUpdate().send(text);
	}

	protected abstract void updateSynthesizer(int value) throws InvalidMidiDataException;

	public void sendToSynthesizer() {
		try {
			updateSynthesizer(getValue());
		} catch (InvalidMidiDataException ex) {
			SynthWideEvents.exception().send(ex);
		}
	}
	
	public void resetToDefault(){
		setValue(defaultValue);
	}

	@Override public String toString() {
		return name + ": " + getFormattedValue();
	}

	public String getMidiDoc() {
		return "non midi";
	}

	public static void dumpDocumentation() {
		for (String line : parameterDocumentation.values()) {
			System.out.println(line);
		}
	}
}
